﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using IQToolkit;

namespace ExpressionMapper
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, ParameterExpression, Expression>;

    public static class MappingExpressionGeneratorExtensions
    {
        private static readonly Type _dictionaryType = typeof(ReferenceDictionary);

        public static Func<TFrom, ReferenceDictionary, TTo> GetMapper<TFrom, TTo>(this MappingExpressionGenerator generator,
                                                             Type fromType, Type toType, Expression<Func<TFrom, TTo>> customMapping)
        {
            Delegate result = GetExpression(generator, fromType, toType, customMapping).Compile();

            Func<TFrom, ReferenceDictionary, TTo> func =
                (from, dictionary) =>
                {
                    try
                    {
                        return (TTo)result.DynamicInvoke(@from, dictionary);
                    }
                    catch (Exception excp)
                    {
                        if (excp.InnerException != null)
                            throw excp.InnerException;
                        else
                            throw;
                    }
                };

            return func;
        }

        public static Func<TFrom, ReferenceDictionary, TTo> GetMapper<TFrom, TTo>(this MappingExpressionGenerator generator,
                                                                                    Expression<Func<TFrom, TTo>> customMapping)
        {
            return GetExpression(generator, customMapping).Compile();
        }

        public static LambdaExpression GetExpression<TFrom, TTo>(this MappingExpressionGenerator generator,
            Type fromType, Type toType, Expression<Func<TFrom, TTo>> customMapping)
        {
            Debug.WriteLine(string.Format("GetMapper From {0} to {1}", fromType.Name, toType.Name));

            ParameterExpression p = Expression.Parameter(fromType, "p");

            Expression customMappingBody = null;
            ParameterExpression dictionary = Expression.Variable(_dictionaryType, "dictionary");
            if (customMapping != null)
            {
                customMappingBody = ExpressionReplacer.Replace(customMapping.Body, customMapping.Parameters[0], p);
            }

            Expression mapper = generator(p, toType, customMappingBody, dictionary);
            if (mapper == null)
            {
                throw new InvalidOperationException(string.Format("Can't map from {0} to {1}", fromType.Name, toType.Name));
            }

            if(mapper == p)
            {
                if (toType != typeof(TTo))
                    mapper = Expression.Convert(p, typeof(TTo));
            }

            LambdaExpression result =
                Expression.Lambda(Expression.GetFuncType(fromType, _dictionaryType, typeof(TTo)), mapper, new[] { p, dictionary });

            Debug.WriteLine(result.ToString());
            return result;
        }


        public static Expression<Func<TFrom, ReferenceDictionary, TTo>> GetExpression<TFrom, TTo>(this MappingExpressionGenerator generator,
                                                                             Expression<Func<TFrom, TTo>> customMapping)
        {
            return (Expression<Func<TFrom, ReferenceDictionary, TTo>>)GetExpression(generator, typeof(TFrom), typeof(TTo), customMapping);
        }
    }
}